﻿using System;
using System.Collections.Specialized;
using System.Linq;
using System.Web.Security;
using rpxnow_library.membership.extensions;

namespace rpxnow_library.membership
{
    public class OpenIdMembershipProvider : System.Web.Security.MembershipProvider
    {
        private string connectionString;
        private string defaultPhoto;
        private string applicationName;

        public override void Initialize(string name, NameValueCollection config)
        {
            if (config == null)
            {
                throw new ArgumentNullException("config");
            }

            if (string.IsNullOrEmpty(name))
            {
                name = "IcklePicklesMembershipProvider";
            }

            base.Initialize(name, config);

            string connectionStringName = Utils.GetInitializationConfigParameter(config, "connectionStringName", "");
            this.connectionString = Utils.GetConnectionStringFromName(connectionStringName);

            this.defaultPhoto = Utils.GetInitializationConfigParameter(config, "defaultAvatar", "");

            string applicationName = Utils.GetInitializationConfigParameter(config, "applicationName", "");

            Utils.HandleExcessAttributes(config);
        }

        public override MembershipUser GetUser(string username, bool userIsOnline)
        {
            if (string.IsNullOrEmpty(username))
                throw new ArgumentNullException("username");

            MembershipUser membershipUser = null;

            try
            {
                using (var ctx = this.LoadContext())
                {
                    var user = OpenIdMembershipProvider.LoadUserFromDisplayName(username, ctx);
                    membershipUser = this.CreateMembershipUser(user);
                    OpenIdMembershipProvider.SetUserIsOnline(userIsOnline, ctx, user);
                }
            }
            catch (Exception)
            {
            }

            return membershipUser;
        }

        public override MembershipUser GetUser(object providerUserKey, bool userIsOnline)
        {
            MembershipUser membershipUser = null;

            try
            {
                using (var ctx = this.LoadContext())
                {
                    var user = OpenIdMembershipProvider.LoadUserFromProviderUserKey(providerUserKey, ctx);
                    membershipUser = this.CreateMembershipUser(user);
                    OpenIdMembershipProvider.SetUserIsOnline(userIsOnline, ctx, user);
                }
            }
            catch (Exception)
            {
            }

            return membershipUser;
        }

        public override string GetUserNameByEmail(string email)
        {
            if (string.IsNullOrEmpty(email))
                throw new ArgumentNullException("email");

            string userName = null;

            using (var ctx = this.LoadContext())
            {
                var userNames = from u in ctx.Users
                                where u.email == email
                                orderby u.displayName
                                select u.displayName;

                userName = userNames.FirstOrDefault();
            }

            return userName;
        }

        public override MembershipUser CreateUser(string username, string password, string email, string passwordQuestion, string passwordAnswer, bool isApproved, object providerUserKey, out MembershipCreateStatus status)
        {
            if (string.IsNullOrEmpty(username))
                throw new ArgumentNullException("username");

            if (providerUserKey == null)
                throw new ArgumentNullException("providerUserKey");

            MembershipUser membershipUser = null;
            status = MembershipCreateStatus.ProviderError;

            try
            {
                using (var ctx = this.LoadContext())
                {
                    if (ctx.UserIdentifiers.Any(ui => ui.identifier == (string)providerUserKey))
                    {
                        status = MembershipCreateStatus.DuplicateProviderUserKey;
                        throw new Exception();
                    }

                    string originalUsername = username;
                    int testIncrementor = 1;
                    while (ctx.Users.Any(u => u.displayName == username))
                    {
                        username = originalUsername + testIncrementor++;
                    }

                    var user = new data.User();
                    user.displayName = username;
                    user.email = email;
                    user.authorized = true;
                    user.created = DateTime.Now;
                    user.lastLogin = DateTime.Now;

                    var userIdentifier = new data.UserIdentifier();
                    userIdentifier.identifier = (string)providerUserKey;
                    user.UserIdentifiers.Add(userIdentifier);

                    ctx.Users.InsertOnSubmit(user);

                    ctx.SubmitChanges();

                    membershipUser = this.CreateMembershipUser(user);
                }
                status = MembershipCreateStatus.Success;
            }
            catch (Exception)
            {
            }

            return membershipUser;
        }

        public MembershipUser CreateUser(string identifier, string providerName, string displayName, string realName, string email, string url, string photo, out MembershipCreateStatus status)
        {
            MembershipUser membershipUser = this.CreateUser(displayName, string.Empty, email, string.Empty, string.Empty, true, identifier, out status);
            if (membershipUser != null)
            {
                using (var ctx = this.LoadContext())
                {
                    var user = OpenIdMembershipProvider.LoadUserFromProviderUserKey(identifier, ctx, false);

                    user.realName = string.IsNullOrEmpty(realName) ? displayName : realName;
                    user.email = email;
                    user.url = url;
                    user.photo = string.IsNullOrEmpty(photo) ? this.BuildDefaultAvatar() : photo;
                    user.photo = OpenIdMembershipProvider.PutDomainOnPhoto(identifier, user.photo);

                    var userIdentifier = user.UserIdentifiers.FirstOrDefault();
                    if (userIdentifier != null)
                    {
                        userIdentifier.providerName = providerName;
                    }

                    ctx.SubmitChanges();

                    membershipUser = this.CreateMembershipUser(user);
                }
            }

            return membershipUser;
        }

        static private string PutDomainOnPhoto(string identifier, string photo)
        {
            if (photo.StartsWith("http://"))
                return photo;

            return new Uri(identifier).GetLeftPart(UriPartial.Authority) + photo;
        }

        public override void UpdateUser(MembershipUser membershipUser)
        {
            try
            {
                using (var ctx = this.LoadContext())
                {
                    var user = OpenIdMembershipProvider.LoadUserFromProviderUserKey(membershipUser.ProviderUserKey, ctx);

                    user.email = membershipUser.Email;
                    user.displayName = membershipUser.UserName;

                    var openIdMembershipUser = membershipUser as OpenIdMembershipUser;
                    if (openIdMembershipUser != null)
                    {
                        user.realName = openIdMembershipUser.realName;
                        user.url = openIdMembershipUser.url;
                        user.photo = openIdMembershipUser.photo;
                    }

                    if (!membershipUser.LastLoginDate.Equals(DateTime.MinValue))
                        user.lastLogin = membershipUser.LastLoginDate;

                    ctx.SubmitChanges();
                }
            }
            catch (Exception)
            {
            }
        }

        public override bool DeleteUser(string username, bool deleteAllRelatedData)
        {
            bool success = false;

            try
            {
                using (var ctx = this.LoadContext())
                {
                    var user = OpenIdMembershipProvider.LoadUserFromDisplayName(username, ctx);
                    ctx.UserIdentifiers.DeleteAllOnSubmit(ctx.UserIdentifiers.Where(i => user.ID == i.fUserID));
                    ctx.UserRoles.DeleteAllOnSubmit(ctx.UserRoles.Where(r => user.ID == r.fUserID));
                    ctx.Users.DeleteOnSubmit(user);

                    ctx.SubmitChanges();
                    success = true;
                }
            }
            catch (Exception)
            {
            }

            return success;
        }

        public override MembershipUserCollection FindUsersByEmail(string emailToMatch, int pageIndex, int pageSize, out int totalRecords)
        {
            throw new NotImplementedException();
        }

        public override MembershipUserCollection FindUsersByName(string usernameToMatch, int pageIndex, int pageSize, out int totalRecords)
        {
            throw new NotImplementedException();
        }

        public override MembershipUserCollection GetAllUsers(int pageIndex, int pageSize, out int totalRecords)
        {
            MembershipUserCollection muc = new MembershipUserCollection();

            using (var ctx = this.LoadContext())
            {
                foreach (var user in ctx.Users.Skip(pageIndex * pageSize).Take(pageSize))
                {
                    muc.Add(this.CreateMembershipUser(user));
                }

                totalRecords = ctx.Users.Count();
            }

            return muc;
        }

        public override int GetNumberOfUsersOnline()
        {
            using (var ctx = this.LoadContext())
            {
                return ctx.Users.Count(u => u.lastLogin.HasValue && u.lastLogin.Value > DateTime.Now.AddMinutes(-System.Web.Security.Membership.UserIsOnlineTimeWindow));
            }
        }

        public override bool UnlockUser(string userName)
        {
            throw new NotImplementedException();
        }

        #region Helpers

        protected MembershipUser CreateMembershipUser(data.User user)
        {
            if (user == null)
                throw new ArgumentNullException();

            return new OpenIdMembershipUser(base.Name,                   // Provider name
                                            user.displayName,            // Username - MUST BE UNIQUE
                                            user.GetPrimaryOpenIdIdentifier(), // providerUserKey
                                            user.email,                  // Email
                                            string.Empty,                // passwordQuestion
                                            string.Empty,                // Comment
                                            user.authorized,             // isApproved
                                            false,                       // isLockedOut
                                            user.created,                // creationDate
                                            user.lastLogin.HasValue ? user.lastLogin.Value : DateTime.MinValue, // lastLoginDate
                                            DateTime.Now,                // lastActivityDate
                                            DateTime.MinValue,           // lastPasswordChangedDate
                                            DateTime.MinValue,           // lastLockoutDate
                                            user.realName,
                                            user.url,
                                            user.photo,
                                            user.GetOpenIdentifiers()
                                        );
        }

        private string BuildDefaultAvatar()
        {
            return System.Web.HttpContext.Current.Request.Url.GetLeftPart(UriPartial.Authority) + System.Web.VirtualPathUtility.ToAbsolute(this.defaultPhoto);
        }

        private rpxnow_library.membership.data.Context LoadContext()
        {
            return Utils.LoadDataContext(this.connectionString);
        }

        private static rpxnow_library.membership.data.User LoadUserFromDisplayName(string username, rpxnow_library.membership.data.Context ctx)
        {
            return OpenIdMembershipProvider.LoadUserFromDisplayName(username, ctx, true);
        }

        private static rpxnow_library.membership.data.User LoadUserFromDisplayName(string username, rpxnow_library.membership.data.Context ctx, bool throwExceptionIfNotFound)
        {
            var user = ctx.Users.SingleOrDefault(u => u.displayName == username);
            if (user == null && throwExceptionIfNotFound)
            {
                throw new Exception();
            }
            return user;
        }

        private static rpxnow_library.membership.data.User LoadUserFromProviderUserKey(object providerUserKey, rpxnow_library.membership.data.Context ctx)
        {
            return OpenIdMembershipProvider.LoadUserFromProviderUserKey(providerUserKey, ctx, true);
        }

        private static rpxnow_library.membership.data.User LoadUserFromProviderUserKey(object providerUserKey, rpxnow_library.membership.data.Context ctx, bool throwExceptionIfNotFound)
        {
            var user = ctx.UserIdentifiers.Where(ui => ui.identifier == (string)providerUserKey).Select(ui => ui.User).FirstOrDefault();
            if (user == null && throwExceptionIfNotFound)
            {
                throw new Exception();
            }
            return user;
        }

        private static void SetUserIsOnline(bool userIsOnline, rpxnow_library.membership.data.Context ctx, rpxnow_library.membership.data.User user)
        {
            if (userIsOnline)
            {
                user.lastLogin = DateTime.Now;
                ctx.SubmitChanges();
            }
        }

        #endregion

        #region Redundant Password Stuff

        public override string GetPassword(string username, string answer)
        {
            throw new NotImplementedException();
        }

        public override int MaxInvalidPasswordAttempts
        {
            get { throw new NotImplementedException(); }
        }

        public override int MinRequiredNonAlphanumericCharacters
        {
            get { throw new NotImplementedException(); }
        }

        public override int MinRequiredPasswordLength
        {
            get { throw new NotImplementedException(); }
        }

        public override int PasswordAttemptWindow
        {
            get { throw new NotImplementedException(); }
        }

        public override MembershipPasswordFormat PasswordFormat
        {
            get { throw new NotImplementedException(); }
        }

        public override string PasswordStrengthRegularExpression
        {
            get { throw new NotImplementedException(); }
        }

        public override bool RequiresQuestionAndAnswer
        {
            get { throw new NotImplementedException(); }
        }

        public override bool RequiresUniqueEmail
        {
            get { throw new NotImplementedException(); }
        }

        public override string ResetPassword(string username, string answer)
        {
            throw new NotImplementedException();
        }

        public override bool ValidateUser(string username, string password)
        {
            throw new NotImplementedException();
        }

        public override bool ChangePassword(string username, string oldPassword, string newPassword)
        {
            throw new NotImplementedException();
        }

        public override bool ChangePasswordQuestionAndAnswer(string username, string password, string newPasswordQuestion, string newPasswordAnswer)
        {
            throw new NotImplementedException();
        }

        public override bool EnablePasswordReset
        {
            get { throw new NotImplementedException(); }
        }

        public override bool EnablePasswordRetrieval
        {
            get { throw new NotImplementedException(); }
        }
        #endregion Redundant Password Stuff

        public override string ApplicationName
        {
            get
            {
                return this.applicationName;
            }
            set
            {
                this.applicationName = value;
            }
        }
    }
}
